﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Tao.FreeGlut;
using Tao.OpenGl;
using Phys.Model;
using System.Diagnostics;
using TWiStEr.Math;
using TWiStEr.OpenGL.Utilities;
using Phys.Model.Affectors;

namespace Phys.UI {
	public partial class PhysicsGLWindow {
		/// <summary>
		/// Holds references to Functions that are "void (IObject)", they draw IObjects with OpenGL, mapped by CLR Type
		/// </summary>
		private static Dictionary<Type, Action<IObject>> drawers = new Dictionary<Type, Action<IObject>>();

		/// <summary>
		/// Initializes the drawer functions.
		/// </summary>
		private void InitDrawers() {
			drawers[typeof(CompositeObject)] = o => {
				foreach (IObject obj in ((CompositeObject)o).Objects) {
					Gl.glPushMatrix();
					obj.Position.glTranslate();
					Draw(obj);
					Gl.glPopMatrix();
				}
			};
			drawers[typeof(SphereObject)] = o => {
				SphereObject s = (SphereObject)o;
				Glut.glutSolidSphere(s.Radius, 36, 36);
			};
			drawers[typeof(RectObject)] = o => {
				RectObject r = (RectObject)o;
				r.Normal.glutSolidSphere(.01, 36, 36);
				r.Normal.glRotateAround(r.Rotation);
				r.Normal.glRotateAsNormal(RectObject.DefaultNormal);
				Gl.glBegin(Gl.GL_QUADS);
				Vector[] corners = { 
					new Vector(-r.Width / 2, -r.Height / 2),
					new Vector(+r.Width / 2, -r.Height / 2),
					new Vector(+r.Width / 2, +r.Height / 2),
					new Vector(-r.Width / 2, +r.Height / 2)
				};
				foreach (Vector v in corners) {
					(r.Normal + v).glNormal();
					v.glVertex2();
				}
				Gl.glEnd();
				//r.Normal.glNormal();
				//Gl.glRectd(r.Width / 2, r.Height / 2, -r.Width / 2, -r.Height / 2);
				RectObject.DefaultNormal.DrawVector();
			};
		}

		/// <summary>
		/// This is because Glut does not support multithreading, and glutPostRedisplay() can only be called from
		/// glutMainLoop() so when a physics update happens we set the PostRedisplay flag.
		/// </summary>
		private bool doPostRedisplay = false;
		/// <summary>
		/// Glut Idle Function, check if PostRedisplay is needed.
		/// </summary>
		private void GL_Idle() {
			if (doPostRedisplay) {
				doPostRedisplay = false;
				Glut.glutPostRedisplay();
			}
		}

		/// <summary>
		/// Glut Display Function, Draws to the display
		/// </summary>
		private void GL_Display() {
			Gl.glClearColor(0.3f, 0.6f, 1f, 1f);
			Gl.glClear(Gl.GL_COLOR_BUFFER_BIT | Gl.GL_DEPTH_BUFFER_BIT);
			Gl.glMatrixMode(Gl.GL_MODELVIEW);
			Gl.glPushMatrix();
			Draw(World);
			Gl.glPopMatrix();

			Glut.glutSwapBuffers();
		}

		/// <summary>
		/// Draws an IObject phyisical object with its associated drawer
		/// </summary>
		/// <param name="obj">the physical IObject to draw</param>
		/// <exception cref="NullReferenceException">will be thrown when obj is null</exception>
		/// <exception cref="KeyNotFoundException">will be thrown when the obj has no associated drawer</exception>
		private static void Draw(IObject obj) {
			// get the function pointer for the object's type, and call it on the object
			Type t = obj.GetType();
			while (t != null) {
				Action<IObject> drawer;
				if (drawers.TryGetValue(t, out drawer)) {
					MaterialAffector mat = obj.Affectors.OfType<MaterialAffector>().SingleOrDefault();
					if (mat != null) {
						mat.Color.glColor();
					} else {
						Gl.glColor3f(1, 1, 1);
					}
					drawer(obj);
					Gl.glPushMatrix();
					Gl.glColor4f(0, 1, 0, 0.1f);
					if((bool)settings["ShowVelocity"]) obj.Velocity.DrawVector();
					Gl.glPopMatrix();
					break;
				}
				t = t.BaseType;
			}
		}
	}
}
